#include "stdafx.h"

#include <fstream>

#include "boost/shared_ptr.hpp"
#include "boost/timer.hpp"
#include "boost/lexical_cast.hpp"
#include "boost/format.hpp"

#include "OpenCV/cv.h"
#include "OpenCV/cxcore.h"
#include "OpenCV/highgui.h"

#include "ColorUtils/Color.h"
#include "ColorUtils/Image.h"
#include "ColorUtils/DataImage.h"
#include "ColorUtils/CheckersImage.h"

#include "MathUtils/Ray.h"

#include "RayTracer/FirstIntersectionResult.h"
#include "RayTracer/SecondIntersectionResult.h"
#include "RayTracer/Intersectable.h"
#include "RayTracer/Plain.h"
#include "RayTracer/FlatRectangle.h"
#include "RayTracer/TexturedRectangle.h"
#include "RayTracer/FlatSphere.h"
#include "RayTracer/TexturedSphere.h"
#include "RayTracer/FlatCylinder.h"
#include "RayTracer/TexturedCylinder.h"
#include "RayTracer/FlatBox.h"
#include "RayTracer/TexturedBox.h"
#include "RayTracer/Light.h"
#include "RayTracer/FlatDisc.h"
#include "RayTracer/TexturedDisc.h"
#include "RayTracer/Scene.h"
#include "RayTracer/GreedyWorld.h"
#include "RayTracer/PinholeCamera.h"
#include "RayTracer/ShadedIntersectable.h"
#include "RayTracer/PointLight.h"
#include "RayTracer/DirectedLight.h"

#include "RayTracer.h"

std::string CutOutGarbage(const std::string& inp)
{
	size_t count = 0;
	while(inp[count] == ' ' || inp[count] == '=')
		count++;

	return inp.substr(count);
}

std::string GetStringParameter(const std::string& str, const std::string& find, const std::string& defualtString)
{
	size_t tempFound;
	std::string tempFind("\n");
	tempFind += find;

	if (str.npos != (tempFound = str.find(tempFind)))
	{
		std::string temp = CutOutGarbage(str.substr(tempFound + tempFind.length()));
		return temp.substr(0, temp.find('\n'));
	}
	else
		return defualtString;
}

std::string GetStringParameter(const std::string& str, const std::string& find)
{
	size_t tempFound;
	std::string tempFind("\n");
	tempFind += find;
	if (str.npos != (tempFound = str.find(tempFind)))
	{
		std::string temp = CutOutGarbage(str.substr(tempFound + tempFind.length()));
		return temp.substr(0, temp.find('\n'));
	}
	else
		throw std::exception("Can't find string.");
}

Point PointFromString(const std::string& str, const std::string& find)
{
	size_t tempFound;
	std::string tempFind("\n");
	tempFind += find;
	if (str.npos != (tempFound = str.find(tempFind)))
	{
		double p1, p2, p3;
		sscanf(CutOutGarbage(str.substr(tempFound + tempFind.length())).c_str(), "%lf %lf %lf", &p1, &p2, &p3);
		return Point(p1, p2, p3);
	}
	else
		throw std::exception("Can't get point.");
}

Point PointFromString(const std::string& str, const std::string& find, const Point& defualtPoint)
{
	size_t tempFound;
	std::string tempFind("\n");
	tempFind += find;
	if (str.npos != (tempFound = str.find(tempFind)))
	{
		double p1, p2, p3;
		sscanf(CutOutGarbage(str.substr(tempFound + tempFind.length())).c_str(), "%lf %lf %lf", &p1, &p2, &p3);
		return Point(p1, p2, p3);
	}
	else
		return defualtPoint;
}

double NumberFromString(const std::string& str, const std::string& find, double defualt)
{
	size_t tempFound;
	std::string tempFind("\n");
	tempFind += find;
	if (str.npos != (tempFound = str.find(tempFind)))
	{
		double num;
		sscanf(CutOutGarbage(str.substr(tempFound + tempFind.length())).c_str(), "%lf", &num);
		return num;
	}
	else
		return defualt;
}

double NumberFromString(const std::string& str, const std::string& find)
{
	size_t tempFound;
	std::string tempFind("\n");
	tempFind += find;
	if (str.npos != (tempFound = str.find(tempFind)))
	{
		double num;
		sscanf(CutOutGarbage(str.substr(tempFound + tempFind.length())).c_str(), "%lf", &num);
		return num;
	}
	else
		throw std::exception("Can't get point.");
}

std::vector<Point> GetPoints(const std::string& inputString, int numberOfPoints)
{
	std::vector<Point> ret;

	for (int i = 0; i < numberOfPoints; i++)
		ret.push_back(PointFromString(inputString, (boost::format("p%1%") % i).str()));

	return ret;
}

Intersectable_ptr GetSurface(const std::string& inputString, Intersectable_ptr obj, Scene_ptr scene)
{
	return Intersectable_ptr(new ShadedIntersectable(obj, scene, 
		PointFromString(inputString, "mtl-specular", Point(1, 1, 1))
		, (int)NumberFromString(inputString, "mtl-shininess", 100.0)
		, PointFromString(inputString, "mtl-ambient", Point(0.1, 0.1, 0.1))
		, PointFromString(inputString, "mtl-emission", Point(0, 0, 0))
		, NumberFromString(inputString, "reflectance", 0)));
}

std::string GetObj(std::string& definition, const std::string& shapeName)
{
	size_t startLocation, endLocation = 0;
	if (definition.npos != (startLocation = definition.find(shapeName)))
	{
		do 
		{
			endLocation = definition.find("\n\n", endLocation + 2);
		} while (endLocation < startLocation);

		if (definition.npos != endLocation) 
		{
			std::string ret = definition.substr(startLocation, endLocation - startLocation);
			definition = definition.substr(0, startLocation) + definition.substr(endLocation + 2, definition.npos);
			return ret;
		}
		else
			return "";
	}
	else
		return "";
}

RayTracer::~RayTracer()
{
	if (_img != NULL)
		cvReleaseImage(&_img);

	cvDestroyWindow("RayTracer");
}

RayTracer::RayTracer() : _img(NULL)
{
	cvNamedWindow("RayTracer", 1);
}

void RayTracer::SaveImage(const std::string& fileName)
{
	if (_img != NULL)
		cvSaveImage(fileName.c_str(), _img);
}

Image_ptr GetTexture(const std::string& defenition)
{
	if (GetStringParameter(defenition, "mtl-type", "flat") == "checkers")
		return Image_ptr(
			new CheckersImage(
				NumberFromString(defenition, "checkers-size", 0.1)
				, PointFromString(defenition, "checkers-diffuse1", Point(1, 1, 1))
				, PointFromString(defenition, "checkers-diffuse2", Point(0.1, 0.1, 0.1))));
	else
		return Image_ptr(
			new DataImage(GetStringParameter(defenition, "texture")));
}

std::string RayTracer::DrawImage(unsigned width, unsigned height, unsigned* counter, std::string definition)
{
	if (_img != NULL)
	{
		if ((_img->width != (int)width) && (_img->height != (int)height))
		{
			cvReleaseImage(&_img);
			_img = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U , 3);
		}
	}
	else
		_img = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U , 3);
	

	boost::shared_ptr<std::vector<Intersectable_ptr>> objects(new std::vector<Intersectable_ptr>());
	boost::shared_ptr<std::vector<Light_ptr>> lights(new std::vector<Light_ptr>);

	Scene_ptr scene(new Scene());
	scene->IntersectionFinder = World_ptr(new GreedyWorld()); //background color
	scene->IntersectionFinder->Init(scene);

	std::string workingString = GetObj(definition, "scene:");
	scene->BackGroundColor = PointFromString(workingString, "background-col", Point(0, 0, 0));
	scene->AmbientLight = PointFromString(workingString, "ambient-light", Point(0, 0, 0));
	unsigned SuperSampleingWidth = (unsigned)NumberFromString(workingString, "super-samp-width", 1);
	
	std::string backGroundFile = GetStringParameter(workingString, "background-tex", "");
	if (backGroundFile != "")
		scene->ImageData = Image_ptr(new DataImage(backGroundFile));

	workingString = GetObj(definition, "camera:");
	Point eye = PointFromString(workingString, "eye");
	PinholeCamera cam(
		Ray(eye, PointFromString(workingString, "direction", PointFromString(workingString, "look-at", Point(0, 0, 1)) - eye))
		, PointFromString(workingString, "up-direction")
		, NumberFromString(workingString, "screen-dist")
		, NumberFromString(workingString, "screen-width", 2.0)
		, NumberFromString(workingString, "screen-width", 2.0) * height / width
		, SuperSampleingWidth);

	cam.Init(scene->IntersectionFinder);

	bool parsed = true;
	while (parsed)
	{
		parsed = false;
		
		if ("" != (workingString = GetObj(definition, "rectangle:")))
		{
			std::vector<Point> points = GetPoints(workingString, 3);
			objects->push_back(
				GetSurface(workingString, 
				Intersectable_ptr(
					GetStringParameter(workingString, "mtl-type", "flat") == "flat"
					?
						new FlatRectangle(points[0],  points[1], points[2]
							, PointFromString(workingString, "mtl-diffuse", Point(0.8, 0.8, 0.8)))
					:
						new TexturedRectangle(points[0], points[1], points[2], GetTexture(workingString)))
				, scene));
			parsed = true;
		}
		
		if ("" != (workingString = GetObj(definition, "disc:")))
		{
			Ray ray(PointFromString(workingString, "center"), PointFromString(workingString, "normal"));
			double radius = NumberFromString(workingString, "radius" , 1);
			objects->push_back(
				GetSurface(workingString, 
				Intersectable_ptr(
					GetStringParameter(workingString, "mtl-type", "flat") == "flat"
					?
						new FlatDisc(ray, radius, PointFromString(workingString, "mtl-diffuse", Point(0.8, 0.8, 0.8)))
					:
						new TexturedDisc(ray, radius, GetTexture(workingString)))
				, scene));
			parsed = true;
		}

		if ("" != (workingString = GetObj(definition, "sphere:")))
		{
			Point center(PointFromString(workingString, "center"));
			double radius = NumberFromString(workingString, "radius" , 1);
			objects->push_back(
				GetSurface(workingString, 
				Intersectable_ptr(
					GetStringParameter(workingString, "mtl-type", "flat") == "flat"
					?
						new FlatSphere(center, radius, PointFromString(workingString, "mtl-diffuse", Point(0.8, 0.8, 0.8)))
					:
						new TexturedSphere(center, radius, GetTexture(workingString)))
				, scene));
			parsed = true;
		}

		if ("" != (workingString = GetObj(definition, "box:")))
		{
			std::vector<Point> points = GetPoints(workingString, 4);
			objects->push_back(
				GetSurface(workingString,
					GetStringParameter(workingString, "mtl-type", "flat") == "flat"
					?
						Intersectable_ptr(
							new FlatBox(points[0], points[1], points[2], points[3]
							, PointFromString(workingString, "mtl-diffuse", Point(0.8, 0.8, 0.8))))
					:
						Intersectable_ptr(
							new TexturedBox(points[0], points[1], points[2], points[3], GetTexture(workingString)))
					, scene));
			parsed = true;
		}

		if ("" != (workingString = GetObj(definition, "cylinder:")))
		{
			Ray ray(PointFromString(workingString, "start"), PointFromString(workingString, "direction"));
			double length = NumberFromString(workingString, "length" , 1), radius =  NumberFromString(workingString, "radius" , 1);
			objects->push_back(
				GetSurface(workingString, 
				Intersectable_ptr(
					GetStringParameter(workingString, "mtl-type", "flat") == "flat"
					?
						new FlatCylinder(ray, length, radius, PointFromString(workingString, "mtl-diffuse", Point(0.8, 0.8, 0.8)))
					:
						new TexturedCylinder(ray, length, radius, GetTexture(workingString)))
				, scene));
			parsed = true;
		}

		if ("" != (workingString = GetObj(definition, "light-point:")))
		{
			lights->push_back(
				Light_ptr(
				new PointLight(scene, 
				PointFromString(workingString, "pos")
				, PointFromString(workingString, "attenuation", Point(1, 0, 0))
				, PointFromString(workingString, "color", Point(1, 1, 1)))
				));
			parsed = true;
		}

		if ("" != (workingString = GetObj(definition, "light-directed:")))
		{
			lights->push_back(
				Light_ptr(
				new DirectedLight(scene, PointFromString(workingString, "direction"), PointFromString(workingString, "color", Point(1, 1, 1)))));
			parsed = true;
		}

		if ("" != (workingString = GetObj(definition, "light-area:")))
		{
			int gridWidth = (int)NumberFromString(workingString, "grid-width");

			std::vector<Point> points = GetPoints(workingString, 3);
			Point intensity(PointFromString(workingString, "color", Point(1, 1, 1)))
				, attenuation(PointFromString(workingString, "attenuation", Point(1, 0, 0)));
			
			points[1] = points[1] - points[0];
			points[2] = points[2] - points[0];

			intensity *= 1.0 / (double)(gridWidth * gridWidth);
			points[1] *= 1.0 / gridWidth;
			points[2] *= 1.0 / gridWidth;
			
			Point start = points[0];
			int y;
			for (int x = 0; x < gridWidth; x++, start += points[1])
				for (y = 0, points[0] = start; y < gridWidth; y++, points[0] += points[2])
					lights->push_back(Light_ptr(new PointLight(scene, points[0], attenuation, intensity)));
			
			parsed = true;
		}
	}
	
	scene->Lights = lights;
	scene->Objects = objects;
	
	
	boost::timer time;
	time.restart();
	cam.Draw(_img, counter);
	cvShowImage("RayTracer", _img);
	return (boost::format("Finished... time elapsed:%1%") % time.elapsed()).str();
}
